package com.uxsino.simo.indicator.retractor;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.uxsino.commons.utils.config.ConfigProp;
import com.uxsino.simo.networkentity.EntityInfo;
import com.uxsino.simo.query.QueryContext;
import com.uxsino.simo.query.QueryTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

// retract:
// <retract indicator="" parser="json_list" prefix="aa###bb###***###cc">
// <column col_id="abc" field="field name"/>
// '***' in prefix match any key of json object or all keys in json array
// in following example, *** match 'shard1' and 'clusterTestSet'.

/*
 * example input: "replicaSets" : { "shard1" : { "hosts" : [ { "addr" : "localhost:27011", "ok" : true, }, { "addr" :
 * "localhost:27021", "ok" : true, } ] }, "clusterTestSet" : { "hosts" : [ { "addr" : "localhost:27001", "ok" : true, },
 * { "addr" : "localhost:27002", "ok" : true, } ] } } To extract all hosts' addr and ok information： <retract
 * indicator="mongodb_cluster_replicaset" parser="json_list" prefix="replicaSets###***###hosts"> <column col_id="addr"
 * field="address" /> <column col_id="ok" field="is_ok" />
 */

public class JSONListRetractor extends ListValueRetractor {
    private static Logger logger = LoggerFactory.getLogger(JSONListRetractor.class);

    @ConfigProp(name = "prefix")
    public String prefix;

    public JSONListRetractor() {
        super();
        prefix = "";
    }

    @Override
    public Object doRetract(EntityInfo entity, QueryContext ctxt, QueryTemplate qt, Object obj) {
        if (obj == null) {
            logger.error("retract object is null");
            return null;
        }
        ArrayList<Map<String, Object>> result = new ArrayList<>();
        List<JSONObject> info = getInfo(obj);

        for (JSONObject jsonObj : info) {
            Map<String, Object> map = new HashMap<>();
            for (ColumnEntry entry : columnEntries) {
                String keyName = entry.index;
                Object joValue = getJSONValue(jsonObj, keyName);
                Object value = entry.retractor.retract(entity, ctxt, qt, joValue);
                map.put(entry.field.getName(), value);
            }
            result.add(map);
        }
        return result;
    }

    private Object getJSONValue(JSONObject jsonObj, String key) {
        String[] keyArr = key.split("###");
        for (int i = 0; i < keyArr.length - 1; i++) {
            jsonObj = (JSONObject) jsonObj.get(keyArr[i]);
        }
        return jsonObj.get(keyArr[keyArr.length - 1]);
    }

    private List<JSONObject> getInfo(Object obj) {
        List<JSONObject> objList = new ArrayList<>();
        if (obj instanceof JSONObject || obj instanceof JSONArray) {
            addToList(objList, obj);
        } else if (obj instanceof String) {
            String objStr = (String) obj;
            if (objStr.startsWith("[") && objStr.endsWith("]")) {
                JSONArray jarr = JSON.parseArray(objStr);
                return getInfo(jarr);
            } else {
                JSONObject jsonObj = JSON.parseObject((String) obj);
                objList.add(jsonObj);
            }
        }

        if (prefix.length() == 0) {
            return objList;
        }
        String[] keyArr = prefix.split("###");

        for (int index = 0; index < keyArr.length; index++) {
            List<JSONObject> tempList = new ArrayList<>();
            if (keyArr[index].equals("***")) {
                for (JSONObject o : objList) {
                    for (String k : o.keySet()) {
                        Object jsonOrJsonArr = o.get(k);
                        addToList(tempList, jsonOrJsonArr);
                    }
                }
            } else {
                for (JSONObject o : objList) {
                    Object jsonOrJsonArr = o.get(keyArr[index]);
                    addToList(tempList, jsonOrJsonArr);
                }
            }
            objList = tempList;
        }

        return objList;
    }

    private void addToList(List<JSONObject> list, Object jsonOrJsonArr) {
        if (jsonOrJsonArr == null)
            return;
        if (jsonOrJsonArr instanceof JSONObject) {
            list.add((JSONObject) jsonOrJsonArr);
        } else {
            JSONArray jsonArr = (JSONArray) jsonOrJsonArr;
            for (Object o1 : jsonArr) {
                if (o1 instanceof JSONObject) {
                    list.add((JSONObject) o1);
                } else if (o1 instanceof JSONArray) {
                    addToList(list, o1);
                } else {
                    logger.error("unknown type in input object: {}", o1.getClass());
                }
            }
        }
    }
}
